//
//  ServerThreadController.m
//  Networking
//
//  Created by Christoph Eßer on 11/24/11.
//  Copyright 2011 __MyCompanyName__. All rights reserved.
//

#import "ServerThreadController.h"
#import "ConnectionListener.h"
#import "Connection.h"
#import "NSObject+ThreadComm.h"


@implementation ServerThreadController
@synthesize messageDelegate;

-(id) init {
    self = [super init];
    
    if (self) {
        connections = [[NSMutableDictionary alloc] initWithCapacity:5];
        abortedConnections = [[NSMutableDictionary alloc] initWithCapacity:5];
        //initialization is done in main thread!
        mainThread = [NSThread currentThread];
        serverThread = nil;
    }
    
    return self;
}


-(void)dealloc {
    [connections release];
	[abortedConnections release];
    [super dealloc];
}

//method is called by connection listener whenever a new connection comes in
-(void) newIncomingConnection: (Connection*) conn {
    NSString *clientName = [NSString stringWithFormat:@"Client%d",arc4random()]; 
    [connections setObject:conn forKey:clientName];
    [conn setDelegate:self];
    
    NSLog(@"Incoming connection id: %@", clientName);
    
    //send connected message to client
    NetworkMessage *msg = [[NetworkMessage alloc] initWithConnected:clientName];
    [self sendMessage: msg toClient:clientName];
    [msg release];
}

-(NSThread*) startServerThreadOnPort: (NSInteger) port{
    if(!serverThread) {
        NSLog(@"starting server...");
        NSValue *portVal = [NSValue valueWithBytes:&port objCType:@encode(int*)];
        
        serverThread = [[NSThread alloc] initWithTarget:self selector:@selector(thread:) object:portVal];
        [serverThread start];
        
        //Add an NSTimer to check the abortedConnection list, remove the outdated connection and send a timeout message
        //Triger the method every 1 minutes
        [NSTimer scheduledTimerWithTimeInterval:300.0
                                         target:self
                                       selector:@selector(checkAbortedConnections:)
                                       userInfo:nil
                                        repeats:YES];

        return serverThread;
    }
    else {
        NSLog(@"Error starting server networking thread: Already running!");
        return nil;
    }
}

-(void) stopServerThread{
    if(serverThread) {
        [serverThread cancel];
    }
}

//inData is Port listening for connections 
-(void)thread:(id)inData
{        
    //No garbage collection here, need autorelease pool!
    NSAutoreleasePool*  pool = [[NSAutoreleasePool alloc] init];
    
    //start listening on network port
    NSInteger port;
    [inData getValue:&port];
    listener = [[ConnectionListener alloc] initWithSTC:self andPort:port];    
    
	// run the runloop!!
	NSLog(@"server now running...");
    while (![serverThread isCancelled]) {
        [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
        [pool drain]; 
        pool = [[NSAutoreleasePool alloc] init]; // don't forget to drain this at the thread end!!
    }
	
	NSLog(@"server now closing...");
	    
    [connections removeAllObjects];
    [serverThread release];
    serverThread = nil;
    
    //close listener
    [listener release];
	
    //clean up memory
    [pool drain];
    NSLog(@"server stopped");
}

//always use this method to send data to make sure the networking thread is used
-(void) sendMessageToAllClients: (NetworkMessage*) msg {
    //check first if networking thread is used, otherwise call networking thread manually 
    if(![[NSThread currentThread] isEqual:serverThread]) {
        [self performSelector:@selector(sendMessageToAllClients:) onThread:serverThread withObject:msg waitUntilDone:NO];
    }
    else {  
        for(Connection *conn in [connections allValues]) {
            [conn sendMessage:msg];
        }
    }
}

//always use this method to send data to make sure the networking thread is used
-(void) sendMessage: (NetworkMessage*) msg toClient: (NSString*) client {
    //check first if networking thread is used, otherwise call networking thread manually 
    if(![[NSThread currentThread] isEqual:serverThread]) {
        [self performSelector:@selector(sendMessage: toClient:) onThread:serverThread waitUntilDone:NO withObjects:msg,client,nil];
    }
    else {  
        [[connections objectForKey:client] sendMessage:msg];
    }
}

//implement delegate methods here:
-(void) incomingMessage:(NetworkMessage*) msg fromConnection: (Connection*)conn {
    NSString *clientID = [[connections allKeysForObject: conn] lastObject];
    
    //handle some messages yourself, e.g. reconnectMessages
    switch ([msg type]) {
        case kReconnectMessage:
            [connections setObject:conn forKey:[msg stringMessage]];
            [connections removeObjectForKey:clientID];
            [abortedConnections removeObjectForKey:clientID];
            NSLog(@"Client reconnected. ID %@ becomes %@ again", clientID, [msg stringMessage]);
            //confirm old id to client:
            //send connected message to client
            NetworkMessage *connmsg = [[NetworkMessage alloc] initWithConnected:[msg stringMessage]];
            [self sendMessage: connmsg toClient:[msg stringMessage]];
            [connmsg release];
            break;
            
        case kDisconnectMessage:
            [connections removeObjectForKey:clientID];
            [messageDelegate performSelector:@selector(clientDisconnected:) onThread:mainThread waitUntilDone:NO withObjects:clientID, nil ];
            [conn release];
            break;
            
        default:
            [messageDelegate performSelector:@selector(incomingMessage:fromClient:) onThread:mainThread waitUntilDone:NO withObjects:msg, clientID, nil ];
            break;
    }
}

//called if connection aborts
-(void) connectionAborted:(Connection*)conn {
    NSString *client = [[connections allKeysForObject: conn] lastObject];
    NSString *timestamp = [NSString stringWithFormat:@"%0.0f", [[NSDate date] timeIntervalSince1970]];
    
    NSLog(@"SERVER: Connection to client %@ aborted", client);
    
    //remember when connection aborted to send timeout after specific time
    [connections removeObjectForKey:client];
    [abortedConnections setObject:timestamp forKey:client];
    //connection object is useless now
    [conn release];
}

//check if aborted conections are not reestablished after specific timeout (here 60 seconds)
-(void)checkAbortedConnections:(NSTimer *) timer
{
    double currentTime = [[NSDate date] timeIntervalSince1970];
    
    for(id key in abortedConnections)
    {
        NSString *timestamp = [abortedConnections objectForKey:key];
        double time = [timestamp doubleValue];
        //If the time inteval is bigger than 1 minute, delete the record in the aborted list.
        if((currentTime - time) > 60)
        {
            [abortedConnections removeObjectForKey:key];
            //Inform the main controller to clean the outdate connection
            [messageDelegate performSelector:@selector(clientDisconnected:) onThread:mainThread waitUntilDone:NO withObjects:key, nil ];
        }
    }
}

@end
